#include <errno.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>

#ifdef _WIN64
#include "conio.h"
#endif

#include "./SerialControl.h"


#ifdef _WIN64
#define TTY_PATH ""
#define STTY_US  ""
#define STTY_DEF ""
#else
#define TTY_PATH            "/dev/tty"
#define STTY_US             "stty raw -echo -F "
#define STTY_DEF            "stty -raw echo -F "
#endif


SerialControl::SerialControl(std::string serial_com, std::string baudrate, bool *system_running)
{
    this->serial_com = serial_com;
    this->baudrate = baudrate;
    this->system_running = system_running;

    sp = new boost::asio::serial_port(this->iosev);

    system(STTY_US TTY_PATH);
}


SerialControl::~SerialControl()
{
    printf("\r\nClose Serial Port\r\n");
    system(STTY_DEF TTY_PATH);
}


// 连接到串口
bool SerialControl::connect(void)
{
    try
    {
        sp->open(this->serial_com, ec);
        if(ec)
        {
            system(STTY_DEF TTY_PATH);
            printf("Error opening %s: %s\n", this->serial_com, strerror(errno));
            return false;
        }
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << '\n';
    }

    return true;
}


// 配置串口与终端输出
bool SerialControl::config(int vmin, int vtime)
{
    using namespace boost :: asio ;
    sp->set_option(serial_port::baud_rate(115200));
    sp->set_option(serial_port::flow_control(serial_port::flow_control::none));
    sp->set_option(serial_port::parity(serial_port::parity::none));
    sp->set_option(serial_port::stop_bits(serial_port::stop_bits::one));
    sp->set_option(serial_port::character_size(8));


    return true;
}

// 给thread封装一层，不然会报错
void SerialControl::start_write_thread(SerialControl *handle)
{
    (handle)->write_thread();
}


// 读写线程分开，写线程
void SerialControl::write_thread(void)
{
    printf("init write thread\r\n");
    while(*(this->system_running))
    {
#ifdef _WIN64
        if (_kbhit())
        {
            // 往串口写数据
            int key = _getch();

            if (key > 0)
            {
                if (this->keyboard_writable)
                {
                    boost::asio::write (*sp, boost::asio::buffer(&key, 1), ec);
                }
            }
            if (key == 3){
                system(STTY_DEF TTY_PATH);
                *(this->system_running) = false;
                printf("Enter Ctrl+C!\r\n");
                break;
            }
        }
        Sleep(1);
#else
        fd_set rfds;

        FD_ZERO(&rfds);
        // 分别把句柄加入读监视集合里去
        FD_SET(0, &rfds);

        int fd_result = select(1, &rfds, nullptr, nullptr, nullptr);  // 注意是最大值加1

        if (fd_result < 0)
        {
            printf("select err[%d][%d]\r\n", fd_result, errno);                               // select函数出错
            perror("ERROR");
            // break;
        }
        else if (fd_result == 0)
        {
            printf("select time out \n"); // 在设定的tv时间内，socket的状态没有发生变化
        }
        else
        {
            if (FD_ISSET(0, &rfds))                      // 先判断一下是哪个句柄可读
            {
                // 往串口写数据
                int key = getchar();

                if (key > 0)
                {
                    printf("Enter:[%c]\r\n", key);
                    if (this->keyboard_writable)
                    {
                        boost::asio::write (*sp, boost::asio::buffer(&key, 1), ec);
                    }
                }
                if (key == 3){
                    system(STTY_DEF TTY_PATH);
                    *(this->system_running) = false;
                    break;
                }
            }
        }
#endif
    }
}


// 给thread封装一层，不然会报错
void SerialControl::start_read_thread(SerialControl *handle)
{
    (handle)->read_thread();
}


// 读写线程分开，读线程
void SerialControl::read_thread(void)
{
    char getchar_buf = 0;

    while (*(this->system_running))
    {
        boost::asio::read(*sp, boost::asio::buffer(&getchar_buf, 1), boost::asio::transfer_exactly(1), ec);
        if (ec) continue;
        if (this->receive_displayable)
        {
            putchar(getchar_buf);
            fflush(stdout);
        }
        std::lock_guard<std::mutex> guard(mt);
        *(recv_buffer + strlen(recv_buffer)) = getchar_buf;
    }
}


// 提取接收缓存中的数据
void SerialControl::read_data(std::string &buff)
{
    // 将接收buffer中的数据读取出来
    std::lock_guard<std::mutex> guard(mt);
    if(strlen(recv_buffer) > 0)
    {
        buff += recv_buffer;
        memset(recv_buffer, 0, RECV_BUFFER_LEN);
    }
}

// 发送指定长度的数据
size_t SerialControl::send_data(const void *data, size_t len)
{
    if ((nullptr == data) or (len == 0)) return 0;
    return boost::asio::write (*sp, boost::asio::buffer(data, len), ec);
}


// 通过串口执行命令，带回车
void SerialControl::send_cmd(std::string &cmd)
{
    boost::asio::write (*sp, boost::asio::buffer(cmd.c_str(), cmd.size()), ec);
    this->send_key_enter();
}


// 发送回车
void SerialControl::send_key_enter(void)
{
    char enter_keycode = 13;
    boost::asio::write (*sp, boost::asio::buffer(&enter_keycode, 1), ec);
}


// 设置键盘是否可以输入
void SerialControl::set_keyboard_writable(bool keyboard_writable)
{
    this->keyboard_writable = keyboard_writable;
}


// 设置是否显示接收到的数据
void SerialControl::set_receive_displayable(bool receive_displayable)
{
    this->receive_displayable = receive_displayable;
}
